package net.orange.architecture.aop;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;

import net.orange.architecture.audit.IAuditInfo;
import net.orange.architecture.audit.IAuditable;
import net.orange.architecture.audit.IAuditor;
import net.orange.architecture.audit.SecurityInfo;
import net.orange.architecture.exception.SecurityException;
import net.orange.architecture.permission.Permission;
import net.orange.architecture.permission.PermissionRule;
import net.orange.architecture.process.ProcessFactory;
import net.orange.architecture.web.Web;
import net.orange.framework.logic.IResourceProcess;
import net.orange.framework.model.Resource;
import net.orange.framework.model.ResourceType;
import net.orange.framework.model.User;

import org.springframework.aop.MethodBeforeAdvice;

/**
 * The action AOP advice which purpose is audit user's action in system. and
 * block the user action if he / she has no authority to perform it.
 */
public class SecurityInterceptor implements MethodBeforeAdvice, IAuditable {

	/**
	 * The observers, it can be log file,database table or others.
	 */
	protected Collection<IAuditor> auditors;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method,
	 *      java.lang.Object[], java.lang.Object)
	 */
	public void before(Method arg0, Object[] arg1, Object arg2)
			throws Throwable {
		User user = Web.getCurrentUser();
		String modual = getClassName(arg2.getClass().getName());
		String method = arg0.getName();

		if (!checkPermission(user, modual, method)) {
			notifyAuditor(new SecurityInfo(modual + "@" + method, Web
					.getCurrentUser()));

			throw new SecurityException("Block in resource url:" + modual + "@"
					+ method);
		}
	}

	/**
	 * Check whether the user has permission to perform the action.
	 * 
	 * @param user
	 *            The user
	 * @param modual
	 *            The mudual name.
	 * @param method
	 *            The method name.
	 * @return whether the user has permission to perform the action.
	 * @throws Exception
	 */
	private boolean checkPermission(User user, String modual, String method)
			throws Exception {
		String _url_0 = modual + "@*";
		String _url_1 = modual + "@" + method.trim();

		Permission permit_0 = checkPermission(user, _url_0);
		Permission permit_1 = checkPermission(user, _url_1);

		return PermissionRule.get(permit_0, permit_1);
	}

	/**
	 * Get the simple class name.
	 * 
	 * @param s
	 *            full The class name include the package.
	 * @return The simple class name.
	 * @throws Exception
	 */
	private String getClassName(String s) throws Exception {
		if (s == null)
			return "";
		return s.substring(s.lastIndexOf(".") + 1, s.length());
	}

	/**
	 * Check whether the user has permission to perform the action.
	 * 
	 * @param user
	 *            The user
	 * @param url
	 *            The URL
	 * @return whether the user has permission to perform the action.
	 * @throws Exception
	 */
	private Permission checkPermission(User user, String url) throws Exception {
		IResourceProcess process = (IResourceProcess) ProcessFactory
				.get(Resource.class);
		Resource resource = process.findByKey(url);

		if (resource != null) {
			if ((resource.getType().equals(ResourceType.Restrict.getValue()) || resource
					.getType().equals(ResourceType.Restrict.getValue()))
					&& user == null) {
				return Permission.forbid;
			}

			if (resource.getType().equals(ResourceType.Restrict.getValue())
					&& !user.inGroup(resource.getGroups())) {
				return Permission.forbid;
			}

			return Permission.allow;

		} else {
			return Permission.nodefine;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.audit.IAuditable#addAuditor(net.orange.architecture.audit.IAuditor)
	 */
	public void addAuditor(IAuditor auditor) {
		if (auditors == null)
			auditors = new ArrayList<IAuditor>();
		auditors.add(auditor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.audit.IAuditable#removeAuditor(net.orange.architecture.audit.IAuditor)
	 */
	public void removeAuditor(IAuditor auditor) {
		if (auditors != null)
			auditors.remove(auditor);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.audit.IAuditable#notifyAuditor(net.orange.architecture.audit.IAuditInfo)
	 */
	public void notifyAuditor(IAuditInfo info) {
		if (auditors != null) {
			for (IAuditor auditor : auditors) {
				if (auditor != null)
					auditor.update(info);
			}
		}
	}

	/**
	 * @return Returns the auditors.
	 */
	public Collection<IAuditor> getAuditors() {
		return auditors;
	}

	/**
	 * @param auditors
	 *            The auditors to set.
	 */
	public void setAuditors(Collection<IAuditor> auditors) {
		this.auditors = auditors;
	}

}
